package com.googlecode.cswish.struts.hibernate.ehcache;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.ConfigurationFactory;
import net.sf.ehcache.config.ConfigurationHelper;
import net.sf.ehcache.exceptionhandler.CacheExceptionHandler;
import net.sf.ehcache.exceptionhandler.ExceptionHandlingDynamicCacheProxy;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.ehcache.internal.regions.EhcacheCollectionRegion;
import org.hibernate.cache.ehcache.internal.regions.EhcacheEntityRegion;
import org.hibernate.cache.ehcache.internal.regions.EhcacheNaturalIdRegion;
import org.hibernate.cache.ehcache.internal.regions.EhcacheQueryResultsRegion;
import org.hibernate.cache.ehcache.internal.regions.EhcacheTimestampsRegion;
import org.hibernate.cache.ehcache.internal.util.HibernateUtil;
import org.hibernate.cache.spi.CacheDataDescription;
import org.hibernate.cache.spi.CollectionRegion;
import org.hibernate.cache.spi.EntityRegion;
import org.hibernate.cache.spi.NaturalIdRegion;
import org.hibernate.cache.spi.QueryResultsRegion;
import org.hibernate.cache.spi.TimestampsRegion;
import org.hibernate.cfg.Settings;

import com.googlecode.cswish.struts.ContextHolder;
import com.googlecode.cswish.struts.spring.GenericJPAService;

public class EhCacheRegionFactory extends org.hibernate.cache.ehcache.EhCacheRegionFactory {
	
	private static final Log LOG = LogFactory.getLog(GenericJPAService.class);
	
	private Configuration configuration;
	
	private static EhCacheRegionFactory instance;
	
	public EhCacheRegionFactory() {
		instance = this;
	}
	
	public static EhCacheRegionFactory getInstance() {
		return instance;
	}

	@Override
	public void start(Settings settings, Properties properties) throws CacheException {
        this.settings = settings;
        if ( manager != null ) {
        	LOG.error("Manager is null, please attemptToRestartAlreadyStartedEhCacheProvider");
            return;
        }

        try {
            String configurationResourceName = null;
            if ( properties != null ) {
                configurationResourceName = (String) properties.get( NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME );
            }
            
            // TODO: load the configuration setting according to the different environment
            
            if ( configurationResourceName == null || configurationResourceName.length() == 0 ) {
                configuration = ConfigurationFactory.parseConfiguration();
            }
            else {
                URL url;
                try {
                    url = new URL( configurationResourceName );
                }
                catch ( MalformedURLException e ) {
                    url = loadResource( configurationResourceName );
                }
                //configuration = HibernateEhcacheUtils.loadAndCorrectConfiguration( url );
                configuration = HibernateUtil.loadAndCorrectConfiguration(url);
            }
            
            custom(configuration);
            

            manager = new CacheManager( configuration );
            mbeanRegistrationHelper.registerMBean( manager, properties );
        }
        catch ( net.sf.ehcache.CacheException e ) {
            if ( e.getMessage().startsWith(
                    "Cannot parseConfiguration CacheManager. Attempt to create a new instance of " +
                            "CacheManager using the diskStorePath"
            ) ) {
                throw new CacheException(
                        "Attempt to restart an already started EhCacheRegionFactory. " +
                                "Use sessionFactory.close() between repeated calls to buildSessionFactory. " +
                                "Consider using SingletonEhCacheRegionFactory. Error from ehcache was: " + e.getMessage()
                );
            }
            else {
                throw new CacheException( e );
            }
        }
    }

	private void custom(Configuration configuration) {
		/*String searchAttrClassName = HibernateNameAttributeExtractor.class.getName();
		for (CacheConfiguration cacheConfig : configuration.getCacheConfigurations().values()) {
			
			Searchable searchable = cacheConfig.getSearchable();
			if (searchable != null) {
				//searchable = new Searchable();
				//cacheConfig.addSearchable(searchable);
				
				for (SearchAttribute searchAttribute : searchable.getSearchAttributes().values()) {
					searchAttribute.className(searchAttrClassName);
				}
			}
		}*/
	}
	
	// TODO: add method to reset the searchAttribute
	
	@Override
	public EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata)
		throws CacheException {
		return new EhcacheEntityRegion( accessStrategyFactory, getCache(regionName), settings, metadata, properties );
	}
	
	@Override
	public NaturalIdRegion buildNaturalIdRegion(String regionName, Properties properties, CacheDataDescription metadata)
		throws CacheException {
		return new EhcacheNaturalIdRegion( accessStrategyFactory, getCache( regionName ), settings, metadata, properties );
	}
	
	@Override
	public CollectionRegion buildCollectionRegion(String regionName, Properties properties, CacheDataDescription metadata)
		throws CacheException {
		return new EhcacheCollectionRegion(
                accessStrategyFactory,
                getCache( regionName ),
                settings,
                metadata,
                properties
        );
	}
	
	@Override
	public QueryResultsRegion buildQueryResultsRegion(String regionName, Properties properties) throws CacheException {
		return new EhcacheQueryResultsRegion( accessStrategyFactory, getCache( regionName ), properties );
	}
	
	@Override
	public TimestampsRegion buildTimestampsRegion(String regionName, Properties properties) throws CacheException {
		return new EhcacheTimestampsRegion( accessStrategyFactory, getCache( regionName ), properties );
	}
	
	// TODO: optimize this method for 
	private Ehcache getCache(String name) throws CacheException {
        try {
            Ehcache cache = manager.getEhcache(name);
            if ( cache == null ) {
                manager.addCache(name);
                cache = manager.getEhcache(name);
            }
            
            // Can't use config.isEnableSaas(), because of the dead-cycle
            boolean enableSaas = !"false".equals(System.getProperty("security.enableSaas"));
            if (enableSaas) {
	            // see DynamicModelManager.enableCrossSite
	            // convert cache to siteCache
	            if (!(cache instanceof SiteCache)) {
	            	SiteCache siteCache = new SiteCache(this, cache);
	            	manager.addDecoratedCacheIfAbsent(siteCache);
	            	cache = siteCache;
	            }
            }
            
            if (LOG.isDebugEnabled()) {
            	LOG.debug("started EHCache region: " + name + ", SITE is " + ContextHolder.get().getSiteName());
            }
            
            //HibernateEhcacheUtils.validateEhcache(cache);
            HibernateUtil.validateEhcache(cache);
            return cache;
        }
        catch ( net.sf.ehcache.CacheException e ) {
            throw new CacheException( e );
        }
    }
	
	public CacheManager getCacheManager() {
		return manager;
	}

	// Refer to CacheManager.addConfiguredCaches
	public Ehcache createCache(String name) {
		CacheConfiguration cacheConfiguration = configuration.getCacheConfigurations().get(name);
		if (cacheConfiguration == null) {
			cacheConfiguration = configuration.getDefaultCacheConfiguration();
		}
        Ehcache cache = new Cache(cacheConfiguration.clone(), null, null);
        cache = applyCacheExceptionHandler(cacheConfiguration, cache);
        return cache;
    }

    private Ehcache applyCacheExceptionHandler(CacheConfiguration cacheConfiguration, Ehcache cache) {
        CacheExceptionHandler cacheExceptionHandler =
        	ConfigurationHelper.createCacheExceptionHandler(cacheConfiguration.getCacheExceptionHandlerFactoryConfiguration());
        cache.setCacheExceptionHandler(cacheExceptionHandler);

        if (cache.getCacheExceptionHandler() != null) {
            return ExceptionHandlingDynamicCacheProxy.createProxy(cache);
        }
        return cache;
    }
    
    // TODO: use better performance solution
 	public void removeAllSiteCache(String siteName) {
 		CacheManager cacheManager = getCacheManager();
 		String prefix = '#' + siteName + '#';				// see SiteCache.java
 		for (String cacheName : cacheManager.getCacheNames()) {
 			if (cacheName.startsWith(prefix)) {
 				cacheManager.removeCache(cacheName);
 			}
 		}
 	}
}